/*******************************************************************************
        
        copyright:		Copyright (C) Mike Reinhold 2010. All rights reserved
        				
        license:		BSD style: $(LICENSE)
        		
        author(s): 		Mike Reinhold
        				
*******************************************************************************/
module tango.util.cipher.modes.CFB;

import tango.util.cipher.Cipher;

debug(UnitTest){
	import tango.util.cipher.AES;
}

/** This class implements the cipher feed back (CFB) block mode */
class CFB: BlockCipherMode {
	private {
		ubyte[] iv, counter;
	}

	/** Null constructor */
	this() {
	}
	this(CFB other){
		(other._initialized) ? this(other.wrappedCipher.dup) : this();
		this.iv = other.iv;
		this.counter = other.counter;
	}
	
	CFB dup() {
		return new CFB(this);
	}
	
	/***************************************************************************
	
			constructor
			
			Params:
			cipher = block cipher to wrap

	***************************************************************************/
	this(BlockCipher cipher) {
		wrappedCipher = cipher;
	}

	
	/***************************************************************************
	
			cipher
			
			Returns:
			the underlying cipher we are wrapping.

	***************************************************************************/
	BlockCipher cipher() {
		return wrappedCipher;
	}

	/***************************************************************************
	
			cipher
			
			Params:
			cipher_ = block cipher to wrap
			
			Remarks:
			Mutator method which sets the wrapped block cipher.

	***************************************************************************/
	void cipher(BlockCipher cipher_) {
		wrappedCipher = cipher_;
	}

	/***************************************************************************
	
			name
			
			Returns:
			the name of the wrapped cipher and its mode.
			
			Remarks:
			If this were wrapping Serpent the result would be "Serpent/CFB"

	***************************************************************************/
	string name() {
		return ((_initialized) ? wrappedCipher.name : "") ~ "CFB";
	}
	
	/***************************************************************************
	
			requiresPadding
			
			Returns:
			does this mode require padding?

	***************************************************************************/
	bool requiresPadding(){
		return false;
	}

	/***************************************************************************
	
			init
			
			Params:
			encrypt = the direction of the cipher
			key =	  symmetric key to be used
			
			Remarks:
			A new IV is generated by this method

	***************************************************************************/
	void init(bool encrypt, SymmetricKey key) {
		init(encrypt, key, new IVParameters());
	}

	/***************************************************************************
	
			init
			
			Params:
			encrypt =  the direction of the cipher
			key =	   symmetric key to be used
			ivParams = the initialization vector to be used
			
			Remarks:
			If the parameters are incorrect an exception will be thrown.

	***************************************************************************/
	void init(bool encrypt, SymmetricKey key, IVParameters ivParams) {
		if(!ivParams)
			invalid(name() ~ ": Block mode requires IV (use ParametersWithIV)");
		if(ivParams.iv.length < blockSize)
			invalid(name() ~ ": IV must be at least as long as cipher block size");

		//initialize the wrapped cipher
		wrappedCipher.init(true, key);

		//prepare the initialization vector and state counter
		iv = ivParams.iv[0 .. blockSize];
		counter = new ubyte[blockSize];
		counter[] = iv;

		_encrypt = encrypt;
		_initialized = true;
	}

	/***************************************************************************
	
			update
			
			Params:
			input_ = the block to be processed
			output_ = the output buffer
						
			Returns:
			the number of bytes processed
			
			Remarks:
			Update the input block based on the current cipher direction and
				place it in the output block.

	***************************************************************************/
	uint update(void[] input_, void[] output_) {
		if(!_initialized)
			invalid(name() ~ ": Block mode not initialized");
		ubyte[] input = cast(ubyte[]) input_, 
				output = cast(ubyte[]) output_;

		uint len = (counter.length > input.length) ? input.length : counter.length;

		if(len > output.length)
			invalid(name() ~ ": Output buffer too short");

		//Encrypt the counter
		wrappedCipher.update(counter, counter);

		//XOR output with plaintext to create ciphertext
		for(int i = 0; i < len; i++)
			counter[i] ^= input[i];

		//store the output
		output[0 .. len] = counter[0 .. len];
		
		//set the counter
		if(!_encrypt){
			counter[0..len] = input[0..len];
		}
		
		//return the number of bytes processed
		return len;
	}

	/***************************************************************************
	
			blockSize
											
			Returns:
			the size of a block as specified by the wrapped cipher

	***************************************************************************/
	uint blockSize() {
		return wrappedCipher.blockSize;
	}

	/***************************************************************************
	
			ivSize
											
			Returns:
			the length of the initialization vector in bytes

	***************************************************************************/
	uint ivSize() {
		return wrappedCipher.ivSize;
	}

	/***************************************************************************
	
			validKeySizes
											
			Returns:
			an array containing the byte length of every possible valid key

	***************************************************************************/
	uint[] validKeySizes() {
		return wrappedCipher.validKeySizes;
	}

	/***************************************************************************
	
			reset
											
			Returns:
			resets the BlockCipherMode and the wrapped cipher

	***************************************************************************/
	void reset() {
		counter[] = iv;
		wrappedCipher.reset();
	}
	
	debug(UnitTest){
		unittest{
			static const string[] test_keys = [
   					"2b7e151628aed2a6abf7158809cf4f3c",
   					"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
   					"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"];
			
			static const string[] test_ivs = [
			        "000102030405060708090a0b0c0d0e0f",
			        "000102030405060708090a0b0c0d0e0f",
			        "000102030405060708090a0b0c0d0e0f"];
			                                  

   			static const string[]
   					test_plaintexts = [
   							"6bc1bee22e409f96e93d7e117393172a" ~ "ae2d8a571e03ac9c9eb76fac45af8e51",
   							"6bc1bee22e409f96e93d7e117393172a" ~ "ae2d8a571e03ac9c9eb76fac45af8e51",
   							"6bc1bee22e409f96e93d7e117393172a" ~ "ae2d8a571e03ac9c9eb76fac45af8e51"];

   			static const string[]
   					test_ciphertexts = [
   							"3b3fd92eb72dad20333449f8e83cfb4a" ~ "c8a64537a0b3a93fcde3cdad9f1ce58b",
   							"cdc80d6fddf18cab34c25909c99a4174" ~ "67ce7f7f81173621961a2b70171d3d7a",
   							"dc7e84bfda79164b7ecd8486985d3860" ~ "39ffed143b28b1c832113c6331e5407b"]; 
   			
   			static const int text_length = 32;

   			CFB c = new CFB(new AES);
   			ubyte[] buffer = new ubyte[text_length];
   			string result;
   			for(int i = 0; i < test_keys.length; i++) {
   				SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(
   						test_keys[i]));
   				IVParameters params = new IVParameters(ByteConverter.hexDecode(test_ivs[i]));

   				// Encryption
   				c.init(true, key, params);
   				for(int j = 0; j < text_length; j += c.blockSize)
   					c.update(
   							ByteConverter.hexDecode(test_plaintexts[i])[j .. j + c.blockSize],
   							buffer[j .. j + c.blockSize]);
   				result = ByteConverter.hexEncode(buffer);
   				assert(result == test_ciphertexts[i], c.name() ~ ": (" ~ result ~ ") != (" ~ test_ciphertexts[i] ~ ")");

   				// Decryption
   				c.init(false, key, params);
   				for(int j = 0; j < text_length; j += c.blockSize)
   					c.update(
   							ByteConverter.hexDecode(test_ciphertexts[i])[j .. j + c.blockSize],
   							buffer[j .. j + c.blockSize]);
   				result = ByteConverter.hexEncode(buffer);
   				assert(result == test_plaintexts[i], c.name() ~ ": (" ~ result ~ ") != (" ~ test_plaintexts[i] ~ ")");
   			}
		}
	}
}
